package cz.cvut.fit.crhonjar.mi.paa.dunedin.solver;

import java.util.Queue;

import cz.cvut.fit.crhonjar.mi.paa.dunedin.State;
import cz.cvut.fit.crhonjar.mi.paa.dunedin.exception.DunedinException;

/**
 * 
 * @author Jarec
 * 
 */
abstract public class Solver {

	protected State finalState = null;
	private boolean initialized = false;

	private State starting;
	protected Queue<State> que;
	protected boolean visited[];

	public Solver() {
		this.initializeQue();
	}

	protected void addToQue(final Queue<State> que, final State tmp)
			throws DunedinException {
		if (!this.wasVisited(tmp)) {
			if (!que.add(tmp)) {
				throw new DunedinException(
						"DUMP - State cannot be added to que!");
			}
		}
	}

	public int getCountOfBucketOperations() {
		return this.finalState == null ? -1 : this.finalState.getDepth();
	}

	public String getName() {
		return this.getClass().getSimpleName();
	}

	/**
	 * @return the buckets
	 */
	public State getStartingState() {
		return this.starting;
	}

	public int getStatesVisited() {
		int v = 0;
		for (final boolean element : this.visited) {
			if (element) {
				v++;
			}
		}
		return v;
	}

	abstract public void initializeQue();

	public void initiate(final State starting) {
		if (starting == null) {
			throw new IllegalArgumentException(
					"Given NULL starting state to Solver!");
		}
		this.starting = starting;
		this.initialized = true;
	}

	/**
	 * @return the initialized
	 */
	public boolean isInitialized() {
		return this.initialized;
	}

	public void solve() throws IllegalAccessException, DunedinException {
		this.solve(false);
	}

	protected void solve(final boolean debug) throws IllegalAccessException,
			DunedinException {
		if (!this.isInitialized()) {
			throw new IllegalAccessException("Solver must be initialized!");
		}
		this.visited = new boolean[this.getStartingState().maxHashCode() + 1];
		this.que.add(this.getStartingState());

		while (!this.que.isEmpty()) {

			State s = this.que.poll();

			if (this.wasVisited(s)) {
				continue;
			}

			if (s.isFinalStateReached()) {
				this.finalState = s;
				break;
			}

			if (debug) {
				s.print();
			}

			this.visit(s);

			for (int i = 0; i < s.getBuckets().size(); i++) {
				if (!s.getBucket(i).isFull()) {
					final State tmp = s.clone();
					tmp.getBucket(i).fill();
					tmp.incrementDepth();
					this.addToQue(this.que, tmp);
				}
			}

			// pour
			for (int i = 0; i < s.getBuckets().size(); i++) {
				for (int j = 0; j < s.getBuckets().size(); j++) {
					if ((i != j) && !s.getBucket(i).isEmpty()
							&& !s.getBucket(j).isFull()) {
						final State tmp = s.clone();
						tmp.pour(tmp.getBucket(i), tmp.getBucket(j));
						tmp.incrementDepth();
						this.addToQue(this.que, tmp);
					}

				}
			}

			for (int i = 0; i < s.getBuckets().size(); i++) {
				if (!(s.getBucket(i).isEmpty())) {
					final State tmp = s.clone();
					tmp.getBucket(i).dump();
					tmp.incrementDepth();
					this.addToQue(this.que, tmp);
				}
			}
			s = null;
		}
		if (this.finalState == null) {
			System.out.println("Solution was not found!");
		}
		this.que = null;
	}

	protected void visit(final State s) throws DunedinException {
		final int hash = s.hashCode();
		if (this.visited[hash]) {
			throw new DunedinException("State was already vidited! Hash: "
					+ hash + " previous hash: " + s.getPrevious().hashCode());
		}
		this.visited[hash] = true;
	}

	protected boolean wasVisited(final State tmp) {
		return this.visited[tmp.hashCode()];
	}

}
